DEV Community

Cover image for Safely Fetching Scoped Variables (while avoiding Scope Injection)
James Moberg
James Moberg

Posted on

Safely Fetching Scoped Variables (while avoiding Scope Injection)

I'm testing some ideas. I'm not sure if I'm on the right path or not, but thought I'd share.

I have some UDF & CFC libraries that we've built over the year and I have some checks to determine whether default application variables exist and use them to override default values. In order to avoid possible "Scope Injection" & errors (when scopes don't exist), I thought I'd attempt to write a function that uses "safe navigation" to verify scope classname, verify key (in the struct keylist) and return the value (w/optional fallback).

I've included a simple example where application.randomVarName_xxx is injected into the URL & FORM scopes on a script that doesn't use an application CFC or CFM file. As a result, dumping the application value actually returns url.application.randomVarName_xxx instead of a non-existent application-scoped value.

Another example is the retrieval of a server variable with a default value (if it doesn't exist).

Here's a little bit of my post-CFSummit2022 discussion of this on Twitter. https://twitter.com/gamesover/status/1578154242031767553

  • Yeah... I tend to use structKeyExists (or struct.keyExists("keyname")) when the key names are dynamic/random... but yeah, isdefined does reach out to all other scopes if it can't find it. Too bad there's no option to limit searching to within a specific scope. - Me
  • Short term that would be an easy UDF to write and share on GitHub. Longer term that would be a nice upgrade to IsDefined() they could implement ala updates to StructNew() - Nolan

Here's the source code proof-of-concept. Let me know your thoughts.

NOTE: I'd provide a direct link to TryCF.com, but I noticed that gists are permanently cached and never refresh. If you want to test it there, please copy-and-paste the code.

https://gist.github.com/JamoCA/7e544f1488a703d868cecc2b7ae5c2ed

<!--- 2022-10-07 getScopedVariable UDF by James Moberg / SunStar Media
GIST: https://gist.github.com/JamoCA/7e544f1488a703d868cecc2b7ae5c2ed
BLOG: https://dev.to/gamesover/safely-fetching-scoped-variables-while-avoiding-scope-injection-2ee3
TWEET: https://twitter.com/gamesover/status/1578522152600424449
This getScopedVariable UDF tests to determine if a valid scope + variable exists w/o throwing an error.
If non-existent, a default value is returned. --->
<cfscript>
// This is only test code.
// The reason for this is to 1) attempt to validate scoped variables when session, client or application variables aren't enabled
// and 2) return a fallback variable if non-existent (instead of throwing an error)
// ACF 2016+ has application this.searchImplicitScopes setting
// Railo/Lucee has applications this.scopeCascading="strict" setting
// More info on Scope Injection and scopes that are affected
// https://www.petefreitag.com/item/834.cfm
// https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/using-coldfusion-variables/about-scopes.html
function getScopedVariable(required string name, required string scope="application", any default="") {
local.scopeClassMap = [
"application": ["ApplicationScope", "ApplicationImpl"]
,"form": ["FormScope", "FormImpl"]
,"url": ["UrlScope", "URLImpl"]
,"session": ["SessionScope", "sessionMemory"]
,"client": ["NoOperClientScope", ""]
,"server": ["ProtectedScope", "ServerImpl"]
,"cookie": ["CookieScope", "CookieImpl"]
,"cgi": ["CgiScope", "CGIImpl", "CGIImplReadOnly"]
];
local.variable = {};
switch(lcase(arguments.scope)) {
case "application":
local.subType = application?.getClass()?.getName();
local.variable = application;
break;
case "form":
local.subType = form?.getClass()?.getName();
local.variable = form;
break;
case "url":
local.subType = url?.getClass()?.getName();
local.variable = url;
break;
case "session":
local.subType = session?.getClass()?.getName();
local.variable = session;
break;
case "client":
try {
local.subType = client?.getClass()?.getName(); // Lucee bug
if (local.keyexists("subType")) local.variable = client;
} catch (any e){};
break;
case "server":
local.subType = server?.getClass()?.getName();
local.variable = server;
break;
case "cookie":
local.subType = cookie?.getClass()?.getName();
local.variable = cookie;
break;
case "cgi":
local.subType = cgi?.getClass()?.getName();
local.variable = cgi;
break;
default:
return arguments.default;
break;
}
// return fallback if subtype doesn't exist or doesn't match classname
if (!local.keyexists("subType") || !arrayfind(local.scopeClassMap[arguments.scope], listlast(local.subType,"."))) {
return arguments.default;
}
// return fallback if key does not exist in the scoped object
if (!listfindnocase(structkeylist(local.variable), arguments.name)){
return arguments.default;
}
return local.variable[arguments.name];
}
// establish a variable name
testVarName = "randomVarName_#gettickcount()#";
// inject variables into common scopes that are searched
url.application = [
"#testVarName#": ["url scope"]
];
form.application = [
"#testVarName#": ["form scope"]
];
writedump(var=url, label="url (w/injected scope-named key)");
writedump(var=form, label="form (w/injected scope-named key)");
// output "application" variable. (This application variable doesn't exist and shouldn't be dumped.)
try {
writedump( var=application[testVarName], label="application.#testVarName# (unsafe)");
} catch (any e) {
writeoutput("<div>Error: Application scope or '#testVarName#' doesn't exist.</div>");
}
// use function to validate scoped variable and return fallback if it doesn't exist.
result = getScopedVariable(name=testVarName, scope="application", default=["default fallback"]);
writedump( var=result, label="getScopedVariable");
// Server scope fallback test
// server[testVarName] = [now()];
// if scoped variable (that isn't searched) doesn't exist, an error is thrown.
try {
writedump( var=server[testVarName] , label="server.#testVarName# (unsafe)");
} catch (any e) {
writeoutput("<div>Error: server scope or '#testVarName#' doesn't exist.</div>");
}
// use function to validate scoped variable and return fallback if it doesn't exist.
result = getScopedVariable(name=testVarName, scope="server", default=[now()]);
writedump( var=result, label="getScopedVariable");
// structdelete(server, testVarName);
</cfscript>

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (2)

Collapse
 
bennadel profile image
Ben Nadel • Edited

So, just looking for clarity, does this only matter if the variable in question does not exist? Meaning, is the url scoped checked for application.foo only if application.foo doesn't exist? Or does it have to do with scope precedence?

Oh, actually I see in the article from Pete that you mentioned, that it only does this if the variable doesn't exist.

Collapse
 
gamesover profile image
James Moberg

Even if the variable does exist, what if it's not from the exact scope that you are expecting it to be from? I've seen some lazy CFML and have helped some developers that weren't aware of what was happening since they tended to reuse the same variable names within multiple scopes. I've now found that it's best to "be explicit" and "trust, but verify". (I credit my "dotNet blood brother" for the "trust, but verify" mantra as he works in a var-typed environment and is constantly encountering issues like this from other developers that he manages.)